package com.service.websocket.handler;

import cn.hutool.core.util.StrUtil;
import com.service.websocket.to.MessageTO;
import com.service.websocket.to.ResponseObject;
import com.service.websocket.to.ReturnTarget;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;

import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author SF2170
 * @description: 长连接消息处理器抽象层
 *               <b>如果有多少种业务就有多少处理者</b>
 * @date 2019/8/26 13:58
 */
@Slf4j
public abstract class AbstractMessageHandler {


    @Autowired
    protected MessageHandler messageHandler;

    /**
     * 生产消息并发送
     *
     *  <b>
     *      这个方法里面在调用{@link AbstractMessageHandler#sendMessage(MessageTO)}发送消息
     *      原因是：
     *      1.有一种情况不会发生消息
     *      2.针对不同情况发送不同消息
     *  </b>
     * @param tuple
     * @return 返回啥意义不大 看需要
     * @throws Exception
     */
    public abstract String produce(MessageTO tuple) throws Exception;

    /**
     * 获取topic
     * @return
     * @throws Exception
     */
    public abstract String getTopic();

    /**
     * 参数预处理 处理和验证参数是否合法
     *  <b>
     *      1.验证参数是否合法，避免非法参数进入队列
     *      2.将前端参数转换为后端参数
     *  </b>
     * @return
     * @throws Exception
     */
    public abstract Object getParams(Object map) throws Exception ;

    /**
     * 端口连接后的处理 如关闭浏览器
     * @return
     * @throws Exception 不抛异常 只打日志 异常抛不出去了
     */
    public abstract void afterConnectionClosed(String winId)  ;


    /**
     * 发送消息给所有用户
     * @param messageTO 推送消息
     */
    protected final void sendMessageToAll(MessageTO messageTO) throws Exception {
        //获取当前的任务Topic 跨topic查询需要使用
        messageTO.setReturnTarget(ReturnTarget.ALL.getValue());
        messageTO.setTime(System.currentTimeMillis()- messageTO.getTime());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.OK.value());
        responseObject.setMessage(ResponseObject.SUCCESS_MESSAGE);
        responseObject.setData(messageTO);
        Set<String> strings = messageHandler.sendMessageToAllUsers(responseObject);
        if (strings.size() > 0){
            log.error("消息发送失败：" + messageTO + " --> " + StrUtil.join(",", strings));
        }
    }

    /**
     * 发送消息给指定用户 多窗口
     * @param messageTO 推送对象
     */
    protected final void sendMessageToUser(MessageTO messageTO) throws Exception {
        messageTO.setReturnTarget(ReturnTarget.USER.getValue());
        //计算耗时
        messageTO.setTime(System.currentTimeMillis()- messageTO.getTime());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.OK.value());
        responseObject.setMessage(ResponseObject.SUCCESS_MESSAGE);
        responseObject.setData(messageTO);
        if (!messageHandler.sendMessageToUser(messageTO.getUserId(), responseObject)){
            log.error("消息发送失败：" +
                    messageTO + " --> 用户id:" + messageTO.getUserId());
        }

    }

    /**
     * 发送消息给指定业务 多窗口
     * @param messageTO 推送对象
     */
    protected final synchronized void sendMessageToBus(MessageTO messageTO) throws Exception {
        messageTO.setReturnTarget(ReturnTarget.BUS.getValue());
        //计算耗时
        messageTO.setTime(System.currentTimeMillis()- messageTO.getTime());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.OK.value());
        responseObject.setMessage(ResponseObject.SUCCESS_MESSAGE);
        responseObject.setData(messageTO);
        if (!messageHandler.sendMessageByBusId(messageTO.getBusId(), responseObject)){
            log.error("消息发送失败：" +
                    messageTO + " --> 业务id:" + messageTO.getBusId());
        }

    }

    /**
     * 发送消息给指定业务 多用户不包含本用户
     * @param messageTO 推送对象
     */
    protected final void sendMessageToBusExceptOneSelf(MessageTO messageTO) throws Exception {
        messageTO.setReturnTarget(ReturnTarget.BUS.getValue());
        //计算耗时
        messageTO.setTime(System.currentTimeMillis()- messageTO.getTime());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.OK.value());
        responseObject.setMessage(ResponseObject.SUCCESS_MESSAGE);
        responseObject.setData(messageTO);
        if (!messageHandler.sendMessageByBusIdExceptOneSelf(messageTO.getBusId(),
                responseObject, messageTO.getUserId())){
            log.error("消息发送失败：" +
                    messageTO + " --> 业务id:" + messageTO.getBusId());
        }

    }
    /**
     * 发送消息给指定业务 多窗口不包含本窗口
     * @param messageTO 推送对象
     */
    protected final void sendMessageToBusExceptMyWin(MessageTO messageTO) throws Exception {
        messageTO.setReturnTarget(ReturnTarget.BUS.getValue());
        //计算耗时
        messageTO.setTime(System.currentTimeMillis()- messageTO.getTime());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.OK.value());
        responseObject.setMessage(ResponseObject.SUCCESS_MESSAGE);
        responseObject.setData(messageTO);
        if (!messageHandler.sendMessageByBusIdExceptMyWin(messageTO.getBusId(),
                responseObject, messageTO.getWindowId())){
            log.error("消息发送失败：" +
                    messageTO + " --> 业务id:" + messageTO.getBusId());
        }

    }

    /**
     * 发送消息给指定窗口
     * @param messageTO 推送对象
     */
    protected final void sendMessageToWin(MessageTO messageTO) throws Exception {
        messageTO.setReturnTarget(ReturnTarget.WIN.getValue());
        messageTO.setTime(System.currentTimeMillis()- messageTO.getTime());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.OK.value());
        responseObject.setMessage(ResponseObject.SUCCESS_MESSAGE);
        responseObject.setData(messageTO);
        if (!messageHandler.sendMessageToWindow(messageTO.getUserId(), responseObject, messageTO.getWindowId())){
            log.error("消息发送失败：" + messageTO + " --> " + messageTO.getUserId() + "-|" + messageTO.getWindowId());
        }
    }

    /**
     * 发送封装消息
     * <p>
     * 这个接口作为推送错误接口
     * 错误信息只推送当前窗口
     * 不支持推送给全部用户
     * </p>
     * @param messageTO 推送消息
     */
    public final void sendErrorMessage(MessageTO messageTO, String errorMessage) {
        messageTO.setReturnTarget(ReturnTarget.WIN.getValue());
        ResponseObject responseObject = new ResponseObject();
        responseObject.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        responseObject.setMessage(errorMessage);
        responseObject.setData(messageTO);
        if (!messageHandler.sendMessageToWindow(messageTO.getUserId(), responseObject, messageTO.getWindowId())){
            log.error("消息发送失败：" + messageTO + " --> " + messageTO.getUserId());
        }
    }

    /**
     * 默认发送消息方法 可复写
     * @throws Exception
     */
    protected void waitMessage(LinkedBlockingQueue<MessageTO> queue) throws Exception {
        while (true) {
            MessageTO take = queue.take();
            sendMessage(take);
        }
    }

    /**
     * 发送消息
     * @param take
     * @throws Exception
     */
    public void sendMessage(MessageTO take) throws Exception {
        String target = take.getReturnTarget();
        take.setData(take.getData());
        switch (ReturnTarget.get(target)){
            //系统消息推送
            case ALL:
                sendMessageToAll(take);
                break;
            //推送给用户
            case USER:
                sendMessageToUser(take);
                break;
            //推送给用户
            case BUS:
                sendMessageToBus(take);
                break;
            //推送给窗口
            case WIN:
                sendMessageToWin(take);
                break;
            default:
                break;
        }
    }

}
